#include "ComboReduct/combo/procedure_repository.h"
#include "ComboReduct/combo/type_tree.h"

namespace combo {
  
  typedef vtree::iterator pre_it;
  typedef vtree::sibling_iterator sib_it;

  void procedure_repository::add(procedure_call_base* pc) {
    std::string name = pc->get_name();
    str_proc_map_const_it spmi = _repo.find(name);
    if(spmi==_repo.end())
      _repo[name]=pc;
    else {
      //if already exists then check if there are equal in content,
      //if so then do not add it
      //but if not equal in content then raise a cassert
      if(*(spmi->second)==*pc) {
	cassert(TRACE_INFO, false, "A procedure named %s has already with a different body been declared", name.c_str());
      }
    }
  }

  void procedure_repository::remove(const std::string& name) {
    _repo.erase(name);
  }

  procedure_call procedure_repository::instance(const std::string& name) const {
    str_proc_map_const_it spmi = _repo.find(name);
    return spmi==_repo.end() ? NULL : spmi->second;    
  }

  bool procedure_repository::does_contain(const std::string& name) const {
    return _repo.find(name)!=_repo.end();
  }

  void procedure_repository::instantiate_procedure_calls(bool wodo) {
    for(str_proc_map_it ip = _repo.begin(); ip != _repo.end(); ++ip)
      instantiate_procedure_calls(ip->second->get_mutable_body(), wodo);
  }
  
  void procedure_repository::instantiate_procedure_calls(vtree& tr,
							 bool wodo) const {
    for(pre_it it = tr.begin(); it != tr.end(); ++it) {
      if(std::string* str=boost::get<std::string>(&*it)) {
	str_proc_map_const_it proc=_repo.find(*str);
	if(proc==_repo.end()) {
	  if(wodo) {
	    MAIN_LOGGER.log(LADSUtil::Logger::DEBUG, "ComboProcedureRepository - Creating definite_object '%s'. Is this a typo?", str->c_str());
	  }
	} else {
	  MAIN_LOGGER.log(LADSUtil::Logger::DEBUG, "ComboProcedureRepository::instantiateProcedureCalls - Replacing procedure name by procedure object (%s)", str->c_str());
	  *it=proc->second;
	}
      }
    }
  }

  bool procedure_repository::infer_types_repo() {
    typedef std::map<procedure_call, type_tree> proc_type;
    typedef proc_type::iterator proc_type_it;
    typedef proc_type::const_iterator proc_type_const_it;
    typedef std::map<procedure_call, argument_type_list> proc_arg_type;

    //for each procedure of the repository is associated
    //1) a type_tree (the apparent type of the procedure, deduced
    //by the way it is used by other procedures), the typedef proc_type
    //2) a list of type_trees corresponding to the types of each argument
    //of the procedure, the typedef proc_arg_type.
    //
    //The algorithm simply go over those 2 maps and fill them by iteratively
    //checking the use context of the procedures
    //as long as the maps changes it keeps iterating.
    //Then once this is done it checks and infers the types of all procedures
    //with the traditional reduce method
    //and checks that there are compatible with the previously infered types

    //infer procedure and argument types of all procedures based on their use
    proc_type pt, pt_tmp;
    proc_arg_type pat, pat_tmp;
    bool all_went_well = true;
    do {
      pt_tmp = pt;
      pat_tmp = pat;
      for(str_proc_map_const_it pi=_repo.begin(); pi!=_repo.end(); ++pi) {
	procedure_call current_pc = pi->second;

	//insert argument_list to pat[current_pc] if not exists already
	if(pat.find(current_pc)==pat.end())
	  pat[current_pc] = argument_type_list();
	argument_type_list& atl = pat.find(current_pc)->second;
	//insert unknown_type at pt[current_pc] if no type is there yet
	if(pt.find(current_pc)==pt.end())
	  pt[current_pc] = type_tree(id::unknown_type);

	//look over the body of the procedure to infer procedure and argument
	//types
	vtree body = pi->second->get_body();
	for(pre_it it = body.begin(); it != body.end(); ++it) {
	  vertex v = *it;
	  //infer procedure types, compare
	  if(is_procedure_call(v)) {
	    procedure_call_base* pc =
	      const_cast<procedure_call_base*>(get_procedure_call(v));

	    //std::cout << "CONTEXTUAL INFER : " << (unsigned int)pc << " : ";
	    //pc->toStream(std::cout, true);
	    //std::cout << std::endl;

	    proc_type_it pc_it = pt.find(pc);
	    type_tree infered_type = infer_vertex_type(body, it, atl);
	    if(pc_it==pt.end()) {
	      pt[pc]=infered_type;
	      pc->set_type_tree(infered_type);
	    }
	    else {
	      type_tree& tt = pc_it->second;
	      tt = get_intersection(tt, infered_type);
	      pc->set_type_tree(tt);
	      if(!is_well_formed(tt))
		all_went_well = false;
	    }
	  }
	  //infer argument types
	  else if(is_argument(v)) {
	    argument a = get_argument(v);
	    type_tree itt = get_intersection(get_arg_type(a, atl),
					     infer_vertex_type(body, it, atl));
	    set_arg_type(itt, a, atl);
	  }
	}
      }
    } while(pt!=pt_tmp || pat!=pat_tmp);

    //infer the type tree of all procedures using get_type_tree and reduce
    //as for a single tree, then compare if the result is compatible
    //(i.e. the intersection is well_formed)
    for(str_proc_map_const_it pi=_repo.begin(); pi!=_repo.end(); ++pi) {
      procedure_call_base* pc = const_cast<procedure_call_base*>(pi->second);

      //std::cout << "TO INFER : " << (unsigned int)pc << " : ";
      //pc->toStream(std::cout, true);
      //std::cout << std::endl;

      type_tree tt = get_type_tree(pc->get_body());

      reduce_type_tree(tt, pat[pc]);

      insert_arg_type_tree(pat[pc], tt);

      //check that tt is compatible with previously infered type by context
      tt = get_intersection(tt, pt[pc]);
      pc->set_type_tree(tt);
      if(!is_well_formed(tt)) {
	all_went_well = false;
	std::stringstream ss;
	pc->toStream(ss, true);
	std::cout << "Type check error in " << ss.str().c_str() << std::endl;
	cassert(TRACE_INFO, false, "Type check error in %s", ss.str().c_str());
      }
    }
    return all_went_well;
  }

  void procedure_repository::clear() {
    _repo.clear();
  }

  std::ostream& procedure_repository::toStream(std::ostream& out, bool with_type) const {
    for(str_proc_map_const_it i = _repo.begin(); i != _repo.end(); ++i) {
      std::stringstream ss;
      i->second->toStream(ss, true);
      out << ss.str();
      if(with_type)
	out << " [" << i->second->get_type_tree() << "]";
      out << std::endl;
    }
    return out;
  }

  void procedure_repository::print(bool with_type) const {
    toStream(std::cout, with_type);
  }

}//~namespace combo

std::ostream& operator<<(std::ostream& out, combo::procedure_repository pr) {
  return pr.toStream(out);
}
